package storage;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.logging.Logger;

import logic.DeadlineParser;

public class TaskMemoryController {
	private static ArrayList<Task> tasks_ = new ArrayList<Task>();
	private static ArrayList<Task> taskTracker_ = new ArrayList<Task>();
	private static int nextId_ = 0;
	private final static String FILE_NAME = "tasks.xml";
	private final static Logger LOGGER = Logger
			.getLogger(TaskMemoryController.class.getName());

	/**
	 * Why are all methods static? An instance of this class is created in the
	 * main-method of TaskMaster.java
	 */

	public static int getNextId() {
		return nextId_;
	}

	public static void init() {
		tasks_ = importTasksFromFile();
	}

	public static void taskTracking(ArrayList<Task> taskList) {
		taskTracker_ = taskList;
	}

	public static int getIdByIndex(int index) {
		return taskTracker_.get(index - 1).getId();
	}

	public static Task createTask(Task task) {
		// assert nextId_ == task.getId();
		tasks_.add(task);
		nextId_ = task.getId() + 1;
		exportTasksToFile();
		return task;
	}

	/**
	 * Explain please
	 */
	public static Task setTask(Task task, int taskId) {
		assert getIndexById(taskId) > -1;
		tasks_.set(getIndexById(taskId), task);
		exportTasksToFile();
		return task;
	}

	public static boolean removeTask(int taskId) {
		assert getIndexById(taskId) > -1;
		tasks_.remove(getIndexById(taskId));
		exportTasksToFile();
		return true;
	}

	public static Task getTaskById(int taskId) {
		assert getIndexById(taskId) > -1;
		return (Task) tasks_.get(getIndexById(taskId)).clone();
	}

	public static ArrayList<Task> getTasks() {
		ArrayList<Task> copy_list = new ArrayList<Task>();
		for (Task task : tasks_) {
			copy_list.add((Task) task.clone());
		}
		return copy_list;
	}

	public static ArrayList<Task> filterByStatus(ArrayList<Task> tasks,
			TaskStatus status, int flip) {
		// flip = 0 filter selects everything with status
		// flip = 1 filter removes everything associated with status
		ArrayList<Task> filteredTasks = new ArrayList<Task>();
		for (Task task : tasks) {
			if (task.getStatus() == status) {
				if (flip == 0) {
					filteredTasks.add(task);
				}
			} else {
				if (flip == 1) {
					filteredTasks.add(task);
				}
			}
		}
		return filteredTasks;
	}

	public static ArrayList<Task> filterByDate(Date date) {
		ArrayList<Task> filteredTasks = new ArrayList<Task>();
		ArrayList<Task> tasks = getTasks();
		for (Task task : tasks) {
			ArrayList<Date> deadlines = DeadlineParser.overlap(date,
					task.getInterval(), task.getAmount(), task.getDeadline());
			for (int i = 0; i < deadlines.size(); i++) {
				filteredTasks.add(new Task(task.getTitle(), deadlines.get(i), 1, 0,
						task.getStatus(), task.getPriority()));
			}
		}
		return filteredTasks;
	}

	public static ArrayList<Task> filterByDateRange(ArrayList<Task> tasks,
			Date first, Date second, int flip) {
		// flip = 0 filter selects everything between date range
		// flip = 1 filter removes everything between date range
		ArrayList<Task> filteredTasks = new ArrayList<Task>();
		for (Task task : tasks) {
			boolean passFilter = true;
			if (first != null) {
				if (task.getDeadline().after(first)) {
					if (flip == 1) {
						passFilter = false;
					}
				} else {
					if (flip == 0) {
						passFilter = false;
					}
				}
			}
			if (second != null) {
				if (task.getDeadline().before(second)) {
					if (flip == 1) {
						passFilter = false;
					}
				} else {
					if (flip == 0) {
						passFilter = false;
					}
				}
			}
			if (passFilter) {
				filteredTasks.add(task);
			}
		}
		return filteredTasks;
	}

	/*
	 * This will be the function that conducts search Method: keywords string will
	 * be tokenized against spaces and for exact matches, we are looking for
	 * titles that match 100% of keywords. Then the fuzzy search algorithm is used
	 * to pad the search results.
	 */

	public static ArrayList<Task> searchByTitleExact(ArrayList<Task> Tasks,
			String search_keywords) {
		if (Tasks.equals(null))
			return null; // solve base case: null
		ArrayList<Task> searchedTasks = new ArrayList<Task>();
		String[] keywords = search_keywords.toUpperCase().split("\\s");
		for (Task task : Tasks) {
			boolean pass_exact_search = true;
			for (String keyword : keywords) {
				if (!task.getTitle().toUpperCase().contains(keyword)) {
					pass_exact_search = false;
					break;
				}
			}
			if (!pass_exact_search)
				continue;
			searchedTasks.add(task);
		}
		return searchedTasks;
	}

	public static ArrayList<Task> searchByTitleFuzzy(ArrayList<Task> Tasks,
			String search_keywords, double cutoff_significance) {
		if (Tasks.equals(null))
			return null; // solve base case: null
		ArrayList<Task> rankedTasks = new ArrayList<Task>();

		TreeMap<Double, List<Integer>> map = new TreeMap<Double, List<Integer>>();
		for (int i = 0; i < Tasks.size(); i++) {
			Task task = Tasks.get(i);
			double similarity_value = compareStrings(task.getTitle(), search_keywords);
			System.out.println(similarity_value + ":" + task.getTitle() + ":"
					+ search_keywords);
			if (similarity_value < cutoff_significance)
				continue;
			List<Integer> ind = map.get(similarity_value);
			if (ind == null) {
				ind = new ArrayList<Integer>();
				map.put(similarity_value, ind);
			}
			ind.add(i);
		}
		List<Integer> indices = new ArrayList<Integer>();
		for (List<Integer> arr : map.values()) {
			indices.addAll(arr);
		}

		for (int index : indices) {
			rankedTasks.add(Tasks.get(index));
		}
		Collections.reverse(rankedTasks);
		return rankedTasks;
	}

	public static ArrayList<Task> unionTaskLists(ArrayList<Task> tasks_a,
			ArrayList<Task> tasks_b) {
		// Union of two task list is the sum of both lists minus the intersects
		for (Task task_b : tasks_b) {
			boolean pass_boolean = true;
			for (Task task_a : tasks_a) {
				if (task_b.equals(task_a)) {
					pass_boolean = false;
					break;
				}
			}
			if (pass_boolean) {
				tasks_a.add(task_b);
			}
		}
		return tasks_a;
	}

	public static ArrayList<Task> disjointTaskLists(ArrayList<Task> tasks_a,
			ArrayList<Task> tasks_b) {
		// Union of two task list is the sum of both lists minus the intersects
		ArrayList<Task> full_list = new ArrayList<Task>();
		for (Task task_a : tasks_a) {
			boolean pass_boolean = true;
			for (Task task_b : tasks_b) {
				if (task_b.equals(task_a)) {
					pass_boolean = false;
					break;
				}
			}
			if (pass_boolean) {
				full_list.add(task_a);
			}
		}
		for (Task task_b : tasks_b) {
			boolean pass_boolean = true;
			for (Task task_i : full_list) {
				if (task_i.equals(task_b)) {
					pass_boolean = false;
					break;
				}
			}
			if (pass_boolean)
				for (Task task_a : tasks_a) {
					if (task_b.equals(task_a)) {
						pass_boolean = false;
						break;
					}
				}
			if (pass_boolean) {
				full_list.add(task_b);
			}
		}
		return full_list;
	}

	/**
	 * Beginning use of open source code from
	 * http://www.catalysoft.com/articles/StrikeAMatch.html
	 */

	/** @return an array of adjacent letter pairs contained in the input string */
	private static String[] letterPairs(String str) {
		int numPairs = str.length() - 1;
		String[] pairs = new String[numPairs];
		for (int i = 0; i < numPairs; i++) {
			pairs[i] = str.substring(i, i + 2);
		}
		return pairs;
	}

	/** @return an ArrayList of 2-character Strings. */
	private static ArrayList wordLetterPairs(String str) {
		ArrayList allPairs = new ArrayList();
		// Tokenize the string and put the tokens/words into an array
		String[] words = str.split("\\s");
		// For each word
		for (int w = 0; w < words.length; w++) {
			// Find the pairs of characters
			String[] pairsInWord = letterPairs(words[w]);
			for (int p = 0; p < pairsInWord.length; p++) {
				allPairs.add(pairsInWord[p]);
			}
		}
		return allPairs;
	}

	/** @return lexical similarity value in the range [0,1] */
	public static double compareStrings(String str1, String str2) {
		ArrayList pairs1 = wordLetterPairs(str1.toUpperCase());
		ArrayList pairs2 = wordLetterPairs(str2.toUpperCase());
		int intersection = 0;
		int union = pairs1.size() + pairs2.size();
		for (int i = 0; i < pairs1.size(); i++) {
			Object pair1 = pairs1.get(i);
			for (int j = 0; j < pairs2.size(); j++) {
				Object pair2 = pairs2.get(j);
				if (pair1.equals(pair2)) {
					intersection++;
					pairs2.remove(j);
					break;
				}
			}
		}
		return (2.0 * intersection) / union;
	}

	/** End of open-source code */

	private static int getIndexById(int taskId) {
		for (int k = 0; k < tasks_.size(); k++) {
			if (tasks_.get(k).getId() == taskId) { // Why get(k)?
				return k;
			}
		}
		return -1;
	}

	private static ArrayList<Task> importTasksFromFile() {
		// Order of variables in file: id, title, deadline, status
		ArrayList<Task> imports = new ArrayList<Task>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(FILE_NAME));
			String newLine;
			String full_text = "";
			while ((newLine = in.readLine()) != null) {
				full_text += newLine;
			}
			// if(!newLine.contains("<TaskList>")) not supposed to have
			if (full_text.contains("<NextId>"))
				nextId_ = Integer.parseInt(full_text.split("<NextId>")[1]
						.split("</NextId>")[0]);
			String task_list_text;
			String[] individual_tasks;
			if (full_text.contains("<TaskList>")) {
				task_list_text = full_text.split("<TaskList>")[1].split("</TaskList>")[0];
				individual_tasks = task_list_text.split("<TaskElement>");
				for (int i = 1; i < individual_tasks.length; i++) {
					String task_i = individual_tasks[i];
					Task importTask = new Task();
					importTask.setId(Integer.parseInt(task_i.split("<id>")[1]
							.split("</id>")[0]));
					importTask.setTitle(task_i.split("<title>")[1].split("</title>")[0]);
					importTask.setDeadline(DateFormat.getDateTimeInstance().parse(
							task_i.split("<deadline>")[1].split("</deadline>")[0]));
					importTask
							.setStatus(TaskStatus.getByValue(task_i.split("<status>")[1]
									.split("</status>")[0]));
					importTask.setPriority(TaskPriority.getByValue(task_i
							.split("<priority>")[1].split("</priority>")[0]));
					imports.add(importTask);
				}
			}

		} catch (ParseException e) {
			throw new Error("PARSEEXCEPTION (Import Tasks)");
		} catch (FileNotFoundException e) {
		} catch (IOException e) {
			throw new Error("IOEXCEPTION (Import Tasks)");
		}
		return imports;
	}

	/**
	 * When is a new task saved? Buffered?
	 */
	private static boolean exportTasksToFile() {
		// Order of variables in file: id, title, deadline, status
		try {
			PrintStream out = new PrintStream(new FileOutputStream(FILE_NAME));
			out.println("<NextId>" + nextId_ + "</NextId>");
			out.println("<TaskList>");
			String t = "\t";
			String endl = "\n";
			for (Task exportTask : tasks_) {
				String exportLine = t + "<TaskElement>" + endl;
				exportLine += t + t + "<id>" + exportTask.getId() + "</id>" + endl;
				exportLine += t + t + "<title>" + exportTask.getTitle() + "</title>"
						+ endl;
				exportLine += t + t + "<deadline>"
						+ DateFormat.getDateTimeInstance().format(exportTask.getDeadline())
						+ "</deadline>" + endl;
				exportLine += t + t + "<status>" + exportTask.getStatus().toString()
						+ "</status>" + endl;
				exportLine += t + t + "<priority>"
						+ exportTask.getPriority().toString() + "</priority>" + endl;
				exportLine += t + "</TaskElement>";
				out.println(exportLine);
			}
			out.println("</TaskList>");
			out.close();
			return true;
		} catch (FileNotFoundException e) {
			throw new Error("File Not Found");
		}
	}
}